<?php
// +----------------------------------------------------------------------
// | Bwsaas
// +----------------------------------------------------------------------
// | Copyright (c) 2015~2020 http://www.buwangyun.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Gitee ( https://gitee.com/buwangyun/bwsaas )
// +----------------------------------------------------------------------
// | Author: buwangyun <hnlg666@163.com>
// +----------------------------------------------------------------------
// | Date: 2020-9-28 10:55:00
// +----------------------------------------------------------------------

namespace app\api\controller\wechat;

use app\common\model\Member;
use app\common\model\WechatOpenToken;
use filter\Inspect;
use think\facade\Log;
use buwang\base\BaseController;
use buwang\facade\WechatMp;
use buwang\facade\WechatProgram;
use app\common\model\MemberMiniapp;
use EasyWeChat\OpenPlatform\Server\Guard;
use EasyWeChat\Kernel\Messages\Text;
use EasyWeChat\Kernel\Messages\Video;
use EasyWeChat\Kernel\Messages\Image;
use EasyWeChat\Kernel\Messages\Article;
use EasyWeChat\Kernel\Messages\Raw;
use EasyWeChat\Kernel\Messages\Voice;
use EasyWeChat\Kernel\Messages\News;
use EasyWeChat\Kernel\Messages\NewsItem;
use EasyWeChat\Kernel\Messages\Transfer;
use Exception;
use GuzzleHttp\Client;
use buwang\facade\File;

class WechatAuthOpen extends BaseController
{
    /**
     * 强制客服消息回复
     * @var boolean
     */
    protected $forceCustom = false;
    /**
     * 消息实例
     */
    protected $wechatApp = null;
    /**
     * 当前小程序或公众号的app_id
     */
    protected $app_id = '';
    /**
     * 租户应用id
     */
    protected $member_miniapp_id = 0;
    /**
     * 是否是小程序
     */
    protected $is_miniapp = false;
    /**
     * 用户的openid
     */
    protected $openid = '';
    /**
     * 公众号id
     */
    protected $toUserName = '';
    /**
     * 微信开放平台推送车票(1次/10分钟)
     * 有了车票要保存下来,获取授权时要用
     * @return json
     */
    public function ticket()
    {
        try {
            $server = WechatMp::openConfig()->server;
            // 处理授权成功事件
            $server->push(function ($message) {
                //Log::write(json_encode($message),'auth_success');
            }, Guard::EVENT_AUTHORIZED);
            // 处理授权更新事件
            $server->push(function ($message) {
                //Log::write(json_encode($message),'auth_update');
            }, Guard::EVENT_UPDATE_AUTHORIZED);
            // 处理授权取消事件
            $server->push(function ($message) {
                //Log::write(json_encode($message),'auth_unauth');
                WechatOpenToken::where(['authorizer_appid' => $message['AuthorizerAppid']])->delete();
            }, Guard::EVENT_UNAUTHORIZED);
            $server->serve();
            return response("success");
        } catch (Exception $e) {
            Log::error($e->getMessage());
        }
    }

    /**
     * 微信开放平台事件接受
     * @return json
     */
    public function server($appid)
    {
        try {
            $this->app_id = $appid;//赋值后可全局使用
            //公众号和小程序开放平台接入验证
            if ($appid == 'wx570bc396a51b8ff8' || $appid == 'wxd101a85aa106f53e') {
                $app = $appid == 'wx570bc396a51b8ff8' ? WechatMp::openConfig()->officialAccount($appid) : WechatMp::openConfig()->miniProgram($appid);
                $app->server->push(function ($message) {
                    switch ($message['MsgType']) {
                        case 'event':
                            return $message['Event'] . 'from_callback';
                            break;
                        case 'text':
                            if ($message['Content'] == "TESTCOMPONENT_MSG_TYPE_TEXT") {
                                return 'TESTCOMPONENT_MSG_TYPE_TEXT_callback';
                            } else {
                                $authCode = explode(":", $message['Content'])[1];
                                return $authCode . "_from_api";
                            }
                            break;
                        default;
                            return new Text("wechat open account verify");
                            break;
                    }
                });
            } else {//wx473ce4dceb809572
                $miniapp = MemberMiniapp::whereOr(['miniapp_appid' => $appid, 'mp_appid' => $appid])->field('id,miniapp_appid,mp_appid')->find();
                if (empty($miniapp)) {
                    return response("fail");
                }
                $this->member_miniapp_id = $miniapp->id;
                if ($miniapp->mp_appid == $appid) {
                    $this->wechatApp = WechatMp::isTypes($this->member_miniapp_id);
                } else {//小程序处理逻辑
                    $this->wechatApp = WechatProgram::isTypes($this->member_miniapp_id);
                    $this->is_miniapp = true;
                }
                if(!$this->wechatApp) return $this->error("微信认证失败,请确认应用已授权");
                $this->wechatApp->server->push(function ($message) {
                    file_put_contents('server.txt', json_encode($message) . PHP_EOL, FILE_APPEND);
                    //TODO 更新微信公众号或小程序用户信息

                    $this->openid = $message['FromUserName'];//用户的openid
                    $this->toUserName = $message['ToUserName'];//该公众号 ID
                    // text, event, image, location
                    if (method_exists($this, ($method = $message['MsgType']))) {
                        $msg = $this->$method($message);
                        if($this->is_miniapp){
                            WechatProgram::isTypes($this->member_miniapp_id)->customer_service->message($msg)->to($message['FromUserName'])->send();
                            return new Transfer();
                        }else{
                            return $msg;
                        }
                    }
                });
                return $this->wechatApp->server->serve()->send();
            }
        } catch (Exception $e) {
            return $this->error("错误信息：".$e->getMessage()."文件：".$e->getFile()."行号：".$e->getLine()."错误码：".$e->getCode());
        }
    }

    /**
     * 文件消息处理
     * @param $message
     * @return bool|Image|News|Raw|Text|Video|Voice|string|null
     */
    protected function text($message)
    {
        return $this->keysReply("wechat_keys#keys#{$message['Content']}", false, $this->forceCustom);
    }
    /**
     * 关键字处理
     * @param string $rule 关键字规则
     * @param boolean $isLast 重复回复消息处理
     * @param boolean $isCustom 是否使用客服消息发送
     * @return boolean|string
     */
    private function keysReply($rule, $isLast = false, $isCustom = false)
    {
        list($table, $field, $value) = explode('#', $rule . '##');
        $data = $this->app->db->name($table)->where([$field => $value])->where(['member_miniapp_id'=>$this->member_miniapp_id])->where(['appid'=>$this->app_id])->find();
        if (empty($data['type']) || (array_key_exists('status', $data) && empty($data['status']))) {
            return $isLast ? false : $this->keysReply('wechat_keys#keys#default', true, $isCustom);
        }
        switch (strtolower($data['type'])) {
            case 'keys':
                //如果type为keys取content字段再次调用回复
                $content = empty($data['content']) ? $data['name'] : $data['content'];
                return $this->keysReply("wechat_keys#keys#{$content}", $isLast, $isCustom);
            case 'text':
                return new Text($data['content']);
                break;
            case 'customservice':
                $msg = new Text($data['content']);
                return $this->wechatApp->customer_service->message($msg)->to($this->openid)->send();
                break;
            case 'voice':
                if (empty($data['voice_url']) || !($mediaId = $this->upload($data['voice_url'], 'voice'))) return null;
                return new Voice($mediaId);
                break;
            case 'image':
                if (empty($data['image_url']) || !($mediaId = $this->upload($data['image_url'], 'image'))) return null;
                if($this->is_miniapp){
                    return $this->wechatApp->customer_service->message(new Image($mediaId))->to($this->openid)->send();
                }else{
                    return new Image($mediaId);
                }
                break;
            case 'news':
                list($news, $articles, $items) = [$this->news($data['news_id']), [], []];
                if (empty($news['articles'])) return null;
                foreach ($news['articles'] as $key=>$vo) {
                    array_push($articles, [
                        'url'   => url("@manage/member.WechatKeyword/previewView", [], false, true) . "?id={$vo['id']}",
                        'title' => $vo['title'], 'image' => $vo['local_url'], 'description' => $vo['digest'],
                    ]);
                    array_push($items, new NewsItem($articles[$key]));
                }
                return new News($items);
                break;
            case 'music':
                if (empty($data['music_url']) || empty($data['music_title']) || empty($data['music_desc'])) return null;
                if(!($mediaId = $this->upload($data['music_image'], 'thumb'))) return null;
                $musicData = [
                    'thumb_media_id' => $mediaId,
                    'description'    => $data['music_desc'],
                    'title' => $data['music_title'],
                    'hq_music_url'     => $data['music_url'],
                    'music_url' => $data['music_url'],
                ];
                $timeC = time();
                return new Raw("<xml><ToUserName><![CDATA[{$this->openid}]]></ToUserName><FromUserName><![CDATA[{$this->toUserName}]]></FromUserName><CreateTime>{$timeC}</CreateTime><MsgType><![CDATA[music]]></MsgType><Music><Title><![CDATA[{$musicData['title']}]]></Title><Description><![CDATA[{$musicData['description']}]]></Description><MusicUrl><![CDATA[{$musicData['music_url']}]]></MusicUrl><HQMusicUrl><![CDATA[{$musicData['hq_music_url']}]]></HQMusicUrl><ThumbMediaId><![CDATA[{$musicData['thumb_media_id']}]]></ThumbMediaId></Music></xml>");
                //return new Raw(json_encode($data));
                break;
            case 'video':
                if (empty($data['video_url']) || empty($data['video_desc']) || empty($data['video_title'])) return null;
                $videoData = ['title' => $data['video_title'], 'desc' => $data['video_desc']];
                if (!($mediaId = $this->upload($data['video_url'], 'video', $videoData))) return null;
                return new Video($mediaId, [
                    'title' => $data['video_title'],
                    'description' => $data['video_desc'],
                ]);
                break;
            default:
                return null;
        }
        //return "不支持的回复规则type类型：".strtolower($data['type']);
    }

    /**
     * 事件消息处理
     * @param $message
     * @return boolean|string
     */
    protected function event($message)
    {
        switch (strtolower($message['Event'])) {
            case 'subscribe':
                //TODO 更新用户信息
                //$this->updateFansinfo(true);
                if (isset($message['EventKey']) && is_string($message['EventKey'])) {
                    if (($key = preg_replace('/^qrscene_/i', '', $message['EventKey']))) {
                        //租户扫码登录自动注册
                        if ($message['EventKey'] == "scan_login") {
                            return $this->doMemberReg($message);
                        }
                        //默认回复
                        return $this->keysReply("wechat_keys#keys#{$key}", false, true);//发送客服消息
                    }
                }
                return $this->keysReply('wechat_keys#keys#subscribe', true, $this->forceCustom);
            case 'unsubscribe':
                //return $this->updateFansinfo(false);
            case 'click':
                return $this->keysReply("wechat_keys#keys#{$message['EventKey']}", false, $this->forceCustom);
            case 'scancode_push':
            case 'scancode_waitmsg':
                if (empty($message['ScanCodeInfo'])) return null;
                if (empty($message['ScanCodeInfo']['ScanResult'])) return null;
                //return "扫码结果：\n\n类型：" . $message['ScanCodeInfo']['ScanType'] . "\n数据：" . $message['ScanCodeInfo']['ScanResult'] . "\n\n你可以自行进行业务逻辑扩展。";
                return $this->keysReply("wechat_keys#keys#{$message['ScanCodeInfo']['ScanResult']}", false, $this->forceCustom);
            case 'scan':
                if (empty($message['EventKey'])) return null;
                //租户扫码登录自动注册
                if ($message['EventKey'] == "scan_login") {
                    return $this->doMemberReg($message);
                }
                //默认回复
                return $this->keysReply("wechat_keys#keys#{$message['EventKey']}", false, $this->forceCustom);
            case 'location':
                $Latitude = $message['Latitude'];
                $Longitude = $message['Longitude'];
                $Precision = $message['Precision'];
                //return "经纬度：" . $Latitude . "-" . $Longitude;
                break;
            default:
                return null;
                //return '不支持的事件' . $message['Event'];
        }
    }

    /**
     * @param $message
     * @return Video
     */
    protected function video($message){
        $mediaId = $message['MediaId'];
        //$thumbMediaId = $message['ThumbMediaId'];
//        $msg = new Video($mediaId, [
//            'title' => '视频',
//            'description' => '视频描述',
//        ]);
        $msg ="视频";
        return $msg;
    }

    /**
     * @param $message
     * @return Video
     */
    protected function shortvideo($message){
        $mediaId = $message['MediaId'];
        //$thumbMediaId = $message['ThumbMediaId'];
//        $msg = new Video($mediaId, [
//            'title' => '小视频',
//            'description' => '小视频描述',
//        ]);
        $msg ="小视频";
        return $msg;
    }
    /**
     * 处理用户发送的地图位置消息
     * @param $message
     * @return string
     */
    protected function location($message){
        $X =  $message['Location_X'];
        $Y = $message['Location_Y'];
        $Label = $message['Label'];
        //地图选择无法回复
        return "地图位置信息:".$X."---".$Y."---".$Label;
    }
    /**
     * 处理用户发送的图片消息
     * @param $message
     * @return Image
     */
    protected function image($message){
        $mediaId = $message['MediaId'];
        return new Image($mediaId);
    }

    /**
     * 处理用户发送的voice
     * @param $message
     * @return Voice
     */
    protected function voice($message){
        $mediaId = $message['MediaId'];
        return new Voice($mediaId);
    }

    /**
     * 处理用户发送的链接信息
     * @param $message
     * @return string
     */
    protected function link($message){
        return '标题:' . $message['Title'] . PHP_EOL . '描述:' . $message['Description'] . PHP_EOL . '链接:' . $message['Url'];
    }

    /**
     * 通过图文ID读取图文信息
     * @param integer $id 本地图文ID
     * @param array $where 额外的查询条件
     * @return array
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    protected function news($id, $where = [])
    {
        $data = $this->app->db->name('WechatNews')->where(['id' => $id])->where($where)->find();
        list($data['articles'], $articleIds) = [[], explode(',', $data['article_id'])];
        $articles = $this->app->db->name('WechatNewsArticle')->whereIn('id', $articleIds)->select();
        foreach ($articleIds as $article_id) foreach ($articles as $article) {
            if (intval($article['id']) === intval($article_id)) array_push($data['articles'], $article);
            unset($article['create_by'], $article['create_at']);
        }
        return $data;
    }
    /**
     * 上传图片永久素材，返回素材media_id
     * @param string $url 文件URL地址
     * @param string $type 文件类型
     * @param array $data 额外信息
     * @return string|null
     */
    protected function upload($url, $type = 'image', $data = []){
        if (!in_array($type, ['image', 'voice', 'video', 'thumb'])) {
            return $this->error('上传素材类型错误！');
        }
        //上传图片到微信服务器,并返回mediaId
        $upload_type = bw_config('base')['upload_type'];
        $thumb_img = substr(parse_url($url)['path'],1);

        //进行存储media资源到表WechatMedia
        //$where = ['md5' => md5($url), 'appid' => $this->app_id];
        $where[] = ['md5', '=', md5($url)];
        $where[] = ['appid', '=', $this->app_id];
        if($this->is_miniapp) $where[] = ['create_at', '>', time()-86400*3-3600];//小程序临时素材只能保存3天

        //查询如果存在就直接返回
        if (($mediaId = $this->app->db->name('WechatMedia')->where($where)->value('media_id'))) return $mediaId;
        $result=[];

        if(true){
            if($upload_type != 'local'){//如果是远程存储下载到本地再上传
                $thumb_path = root_path('public').$thumb_img;

                if (!file_exists($thumb_path)) {//如果本地下载过这个文件就不再下载
                    if(File::mk_dir(dirname($thumb_path))){
                        //下载文件START

                        try{
                            $client = new Client(['verify'=>false]);
                            $client->get($url,['save_to'=>$thumb_path]);
                        }catch(\Exception $e){
                            return $this->error($e->getMessage());
                        }
                        //下载文件END
                    }
                }
            }else{//如果是本地存储模式
                $thumb_path = root_path('public').$thumb_img;
            }
            if(file_exists($thumb_path)){
                try {
                    if($this->is_miniapp){
                        //图片（image）: 2M，支持 JPG 格式
                        $thumb = $this->wechatApp->media->uploadImage($thumb_path);
                    }else{
                        switch ($type){
                            case 'image':
                                $thumb = $this->wechatApp->media->uploadImage($thumb_path);
                                break;
                            case 'thumb':

                                $thumb = $this->wechatApp->media->uploadThumb($thumb_path);
                                break;
                            case 'video':
                                $thumb = $this->wechatApp->media->uploadVideo($thumb_path, $data['title'], $data['desc']);
                                break;
                            case 'voice':
                                $thumb = $this->wechatApp->media->uploadVoice($thumb_path);
                                break;
                        }
                    }
                }catch (Exception $e){
                    return $this->error("上传失败");
                }

                if(empty($thumb['media_id']) && empty($thumb['thumb_media_id'])){
                    return $this->error("上传资源到微信服务器失败,类型为：".$type);
                }

                $result['media_id'] = isset($thumb['media_id']) ? $thumb['media_id'] : $thumb['thumb_media_id'];
                $result['media'] = json_encode($thumb);

            }
        }

        try {
            $mediaData =[
                'local_url' => $url, 'md5' => md5($url), 'appid' => $this->app_id, 'type' => $type,
                'media_url' => isset($thumb['url']) ? $thumb['url'] : '', 'media_id' => $result['media_id'],
                'media'     =>  $result['media'],'create_at'=> time()
            ];
            if (($info = $this->app->db->name('WechatMedia')->where($where)->find()) && !empty($info)) {
                $this->app->db->name('WechatMedia')->strict(false)->where($where)->update($mediaData);
            } else {
                $this->app->db->name('WechatMedia')->strict(false)->insertGetId($mediaData);
            }
        }catch (Exception $e){
            return $this->error("更新wechatMedia数据失败".$e->getMessage());
        }
        return $result['media_id'];
    }
    /**
     * 处理关注事件
     *
     * @param $message
     * @return void
     */
    protected function doSubscribe($message)
    {
        //租户扫码登录自动注册
        if ($message['EventKey'] == "qrscene_scan_login") {
            return $this->doMemberReg($message);
        }
        return "关注事件:" . $message['EventKey'];
    }

    /**
     * 处理租户扫码登录自动注册
     *
     * @param $message
     * @return void
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    protected function doMemberReg($message)
    {
        $openid = Inspect::filter_escape($message['FromUserName']);  //openid
        $member = Member::where(['openid' => $openid])->field('openid,username')->find();
        $msg = bw_config('web_config.web_name');
        if ($member) {
            $member->ticket = Inspect::filter_escape($message['Ticket']);  //Ticket
            $member->save();
            return '您好' . $member['username'] . ',您刚刚登录了「' . $msg . '」';
        } else {
            try {
                return '您好，请先注册账号，登录后绑定微信，再使用微信扫码登录！';
//                $rdm_code = get_num_code(8);
//                $param = [
//                    'username' => 'mp' . $rdm_code,
//                    'nickname' => 'mp' . $rdm_code,
//                    'openid' => $openid,
//                    'login_ip' => request()->ip(),
//                    'login_time' => time(),
//                    'password' => 'a' . $rdm_code,
//                    'safe_password' => '123456',
//                    'ticket' => Inspect::filter_escape($message['Ticket']),
//                ];
//                $res = \app\manage\model\Member::createAdmin($param, 0, true, true, true);
//                if ($res) return "你好,你刚刚注册了「'.$msg.'」帐号,用户名：{$param['username']},密码：a{$rdm_code}";
//                else return "错误提示：" . \app\manage\model\Member::getError();
            } catch (\Exception $e) {
                return "系统错误";//.$e->getMessage()."行号：".$e->getFile().$e->getLine()
            }
        }
        return "自动登录失败";
    }
}